package net.tools.web;

import java.io.*;
import java.net.*;
import java.security.*;

import javax.crypto.*;
import javax.crypto.spec.*;

import org.apache.commons.pool.impl.*;

class SessionProcessor
{

	private StackKeyedObjectPool objPool;

	private long sendSeq, recvSeq;

	private TunnelResponse lastSendResp, lastRecvResp;

	private SecretKey aesKey;

	private Socket s;

	private InputStream sin;

	private OutputStream sout;

	void init(PrivateKey priKey, InputStream in, OutputStream out, StackKeyedObjectPool objPool)
		throws Exception
	{
		this.objPool = objPool;
		if(lastSendResp == null)
			lastSendResp = (TunnelResponse) objPool.borrowObject(ApplicationProcessor.TUNNEL_RESPONSE);
		if(lastRecvResp == null)
			lastRecvResp = (TunnelResponse) objPool.borrowObject(ApplicationProcessor.TUNNEL_RESPONSE);
		Cipher rsaDecrypt = null;
		TunnelCommand cmd = null;
		SecureRandom secRand = null;
		Cipher aesEncrypt = null;
		try
		{
			rsaDecrypt = (Cipher) objPool.borrowObject(ApplicationProcessor.RSA_CIPHER);
			rsaDecrypt.init(Cipher.DECRYPT_MODE, priKey);
			CipherInputStream cin = new CipherInputStream(in, rsaDecrypt);
			cmd = (TunnelCommand) objPool.borrowObject(ApplicationProcessor.TUNNEL_COMMAND);
			cmd.readFrom(cin);
			sendSeq = cmd.getSeq();
			secRand = (SecureRandom) objPool.borrowObject(ApplicationProcessor.SEC_RAND);
			recvSeq = secRand.nextLong();
			aesEncrypt = (Cipher) objPool.borrowObject(ApplicationProcessor.AES_CIPHER);
			aesKey = new SecretKeySpec(cmd.getData(), 0, cmd.getLen(), "aes");
			lastSendResp.setSeq(recvSeq);
			lastSendResp.setStatus(TunnelResponse.SUCCESS);
			lastSendResp.setCmd(TunnelCommand.INITKEY);
			aesEncrypt.init(Cipher.ENCRYPT_MODE, aesKey);
			CipherOutputStream cout = new CipherOutputStream(out, aesEncrypt);
			lastSendResp.writeTo(cout);
			cout.flush();
		}
		finally
		{
			if(rsaDecrypt != null)
				objPool.returnObject(ApplicationProcessor.RSA_CIPHER, rsaDecrypt);
			if(cmd != null)
				objPool.returnObject(ApplicationProcessor.TUNNEL_COMMAND, cmd);
			if(secRand != null)
				objPool.returnObject(ApplicationProcessor.SEC_RAND, secRand);
			if(aesEncrypt != null)
				objPool.returnObject(ApplicationProcessor.AES_CIPHER, aesEncrypt);
		}
	}

	void processCommand(InputStream in, OutputStream out) throws Exception	//return false to invalidate session
	{
		Cipher aesCrypt = null;
		TunnelCommand cmd = null;
		try
		{
			aesCrypt = (Cipher) objPool.borrowObject(ApplicationProcessor.AES_CIPHER);
			aesCrypt.init(Cipher.DECRYPT_MODE, aesKey);
			CipherInputStream cin = new CipherInputStream(in, aesCrypt);
			cmd = (TunnelCommand) objPool.borrowObject(ApplicationProcessor.TUNNEL_COMMAND);
			cmd.readFrom(cin);
			aesCrypt.init(Cipher.ENCRYPT_MODE, aesKey);
			CipherOutputStream cout = new CipherOutputStream(out, aesCrypt);
			TunnelResponse lastResp;
			{
				long seq;
				//synchronized(this){//sync sendSeq, recvSeq, low probability attack, ignore
				if(cmd.getCmd() != TunnelCommand.RECEIVE)
				{
					lastResp = lastSendResp;
					seq = sendSeq;
				}
				else
				{
					lastResp = lastRecvResp;
					seq = recvSeq;
				}
				if(cmd.getSeq() == seq)
				{
					lastResp.writeTo(cout);
					cout.flush();
					return;
				}
				if(cmd.getSeq() != ++seq)
					throw new IOException("seq not equal lastseq + 1.  Maybe attack?");
				if(cmd.getCmd() != TunnelCommand.RECEIVE)
					sendSeq = seq;
				else
					recvSeq = seq;
				//}//sync sendSeq, recvSeq, low probability attack, ignore
				lastResp.setSeq(seq);
				lastResp.setCmd(cmd.getCmd());
			}
			try
			{
				switch(cmd.getCmd())
				{
				case TunnelCommand.CONNECT:
					s = new Socket(cmd.getHost(), cmd.getPort());
					sout = s.getOutputStream();
					sin = s.getInputStream();
					lastResp.setStatus(TunnelResponse.SUCCESS);
					break;
				case TunnelCommand.SEND:
					sout.write(cmd.getData(), 0, cmd.getLen());
					lastResp.setStatus(TunnelResponse.SUCCESS);
					break;
				case TunnelCommand.RECEIVE:
					lastResp.setLen(sin.read(lastResp.getData()));
					lastResp.setStatus(TunnelResponse.SUCCESS);
					break;
				case TunnelCommand.CLOSE:
					sout.close();
					sin.close();
					s.close();
					break;
				}
				lastResp.setStatus(TunnelResponse.SUCCESS);
			}
			catch(IOException e)
			{
				lastResp.setStatus(TunnelResponse.FAIL);
			}
			lastResp.writeTo(cout);
			cout.flush();
		}
		finally
		{
			if(aesCrypt != null)
				objPool.returnObject(ApplicationProcessor.AES_CIPHER, aesCrypt);
			if(cmd != null)
				objPool.returnObject(ApplicationProcessor.TUNNEL_COMMAND, cmd);
		}
	}

	protected void finalize() throws Throwable
	{
		if(lastSendResp != null)
			objPool.returnObject(ApplicationProcessor.TUNNEL_RESPONSE, lastSendResp);
		if(lastRecvResp != null)
			objPool.returnObject(ApplicationProcessor.TUNNEL_RESPONSE, lastRecvResp);
	}

}
